function defaultCRUD(renderTo,url,params) {
    var rnd= Math.random().toString().slice(2);
    $(renderTo).append('<div id="crudHeader'+rnd+'"/>');
    $(renderTo).append('<div id="crudBody'+rnd+'"/>');
    var crud = new CRUDrenderer("#crudBody"+rnd,url);

    if (params == undefined) {
        params = { 
            create:true,
            update:true,
            'delete':true,
            'default':'read',
            'noinit':false
        };
    }

    crudParams = {};
    if (params.update == false) { crudParams.showEdit = false; }
    if (params['delete'] == false) { crudParams.showDelete = false; }
    crud.setParams(crudParams);

    crudHeader = {
        browse:function(crud) {
            crud.refresh(function() { crud.renderRead(); } );
        }
    };
    if (params.create != false) {
        crudHeader.create = function(crud) { crud.renderUpdate(); };
    }
    if (params.update != false || params.create != false) {
        crud.setHeader("#crudHeader"+rnd,crudHeader);
    }
    if (params.noinit != true) {
        if (params['default'] == 'create' && params.create != false) {
            crud.initUpdate();
        } else {
            crud.initRead();
        }
    }

    crud.errorHandler = function(source,data) {
        alert(data.error);
    }

    return crud;
}

function CRUDrenderer(renderTo,url) {
    this.renderTo = renderTo;
    this.url = url;
    var self = this;
    
    this.refresh = function(callback) {
        $.getJSON(self.url,function(data) {
            if (data.error != undefined) {
                if (self.errorHandler != undefined) {
                    self.errorHandler("refresh",data);
                }
            } else {
                self.attributes = data.attributes;
                self.collection = data.collection;
                if (self.onRefresh) {
                    self.onRefresh();
                }
                if (callback != undefined) {
                    callback();
                }            
            }
        });
    };

    this.initRead = function() {
        this.refresh(function() { self.renderHeader(); self.renderRead(); });
    }

    this.refreshRead = function() {
        this.refresh(function() { self.renderRead(); } );
    }

    this.initUpdate = function(id) {
        if (id != undefined) {
           this.refresh(function() { self.renderHeader(); self.renderUpdate(id); } )
        } else {
            this.refresh(function() { self.renderHeader(); self.renderUpdate(); } )
        }
    }
}

// params for *Renderers
CRUDrenderer.prototype.setParams = function(params) {
    this.params = params;
}

CRUDrenderer.prototype.setHeader = function(renderHeaderTo,headerItems) {
    this.renderHeaderTo = renderHeaderTo;
    this.headerItems = headerItems;
}

CRUDrenderer.prototype.renderRead = function() {
    var wrappers;
    wrappers = this.getWrappers(this.readRenderer("wrapper",null) );
    var wrapperOuter = wrappers[0];
    var wrapperInner = wrappers[1];

    wrappers =  this.getWrappers(this.readRenderer("attributesWrapper",null) );
    var attrWrapperOuter = wrappers[0];
    var attrWrapperInner = wrappers[1];
    
    if (attrWrapperOuter == null) {
        attrWrapperInner = wrapperInner;
    }
    attrWrapperInner.append( this.readRenderer("beforeAttributes",null) );
    
    for (var i in this.attributes) {
        attrWrapperInner.append(
            this.readRenderer("attribute",this.attributes[i]) );
    }
    attrWrapperInner.append( this.readRenderer("afterAttributes",null) );
    
    if (attrWrapperOuter != null) {
        wrapperInner.append(attrWrapperOuter);
    }

    wrappers =  this.getWrappers(this.readRenderer("collectionWrapper",null) );
    var colWrapperOuter = wrappers[0];
    var colWrapperInner = wrappers[1];
    if (colWrapperOuter == null) {
        colWrapperInner = wrapperInner;
    }

    for (var i in this.collection) {
        wrappers = this.getWrappers(this.recordRenderer("recordWrapper",this.collection[i].id) );
        recWrapperOuter = wrappers[0];
        recWrapperInner = wrappers[1];
        if (recWrapperOuter == null) {
            recWrapperInner = colWrapperInner;
        }
        recWrapperInner.append(this.recordRenderer("record",this.collection[i]));
        if (recWrapperOuter != null) {
            colWrapperInner.append(recWrapperOuter);
        }
    }
    if (colWrapperOuter != null) {
        wrapperInner.append(colWrapperOuter);
    }
    $(this.renderTo).html(wrapperOuter);
}

CRUDrenderer.prototype.renderHeader = function() {
    if (this.renderHeaderTo == undefined) { return; }
    var renderTo = this.renderHeaderTo;

    var wrappers;
    wrappers = this.getWrappers(this.headerRenderer("wrapper",null));
    var wrapperOuter = wrappers[0];
    var wrapperInner = wrappers[1];
    wrapperInner.append(this.headerRenderer("beforeItems",null));
    for (var name in this.headerItems) {
        wrapperInner.append(this.headerRenderer("item",[name,this.headerItems[name]]));
    }
    wrapperInner.append(this.headerRenderer("afterItems",null));

    $(renderTo).html(wrapperOuter);
}

CRUDrenderer.prototype.renderUpdate = function (id) {
    if (id == undefined) { this._update_id = null; }
    else { this._update_id = id; }
    var items = {};
    for (var i in this.attributes) {
	    items[this.attributes[i]] = null;
    }
    if (id != undefined) {
        for (var i in this.collection) {
            if (this.collection[i].id == id) {
                var record = this.collection[i];
                for (var key in record) {
                    items[key] = record[key];
                }
                break;
            }
        }
    }
    // id is either null or same as items[id]
    var wrappers;
    wrappers = this.getWrappers(this.updateRenderer("wrapper",id));
    wrapperOuter = wrappers[0];
    wrapperInner = wrappers[1];
    wrapperInner.append(this.updateRenderer("beforeItems",id));
    for (var key in items) {
        wrapperInner.append(this.updateRenderer("item",[key,items[key]]));
    }
    wrapperInner.append(this.updateRenderer("afterItems",id));

    $(this.renderTo).html(wrapperOuter);
    delete this._update_id;
}

CRUDrenderer.prototype.getWrappers = function (wrapper) {
    wrapperOuter = wrapper;
    wrapperInner = wrapper;
    if ($.isArray(wrapper)) {
        wrapperOuter = wrapper[0];
        var selector = wrapper[1];
	wrapperOuter.each(function() {
		if ( $(this).is(selector) ) {
			wrapperInner = $(this);
			return false;
		}
		var found = $(this).find(selector);
		if (found.size() > 0) {
			wrapperInner = found;
			return false;
		}
	});
    }
    return [wrapperOuter,wrapperInner];
}

CRUDrenderer.prototype.recordRenderer = function(type,data) {
    if (type == "recordWrapper") {
        return this.readRenderer("recordWrapper",data);
    } else if (type == "record") {
        var ret = $(this.readRenderer("beforeItems",data.id) );
            
        for (var j in this.attributes) {
            var attrName = this.attributes[j];
            if (data[attrName] !== undefined) {
                ret = ret.add(this.readRenderer("item",[attrName,data[attrName]]) );
             }
        }
        ret = ret.add( this.readRenderer("afterItems",data.id) );
        return ret;
    }
}
    
CRUDrenderer.prototype.readRenderer = function(type,data) {
    if (type == "wrapper") {
        return $("<table></table>");
    } else if (type == "attributesWrapper") {
        return $("<tr></tr>");
    } else if (type == "attribute") {
        return $("<th>"+data+"</th>");
    } else if (type == "afterAttributes") {
        var ret = "";
        if (this.params == undefined || this.params.showDelete != false ) {
            ret += "<th>delete</th>";
        }
        if (this.params == undefined || this.params.showEdit != false) {
            ret += "<th>edit</th>"
        }
        return $(ret);
    } else if (type == "collectionWrapper") {
        return null;
    } else if (type == "recordWrapper") {
        return $("<tr></tr>");
    } else if (type == "item") {
        return $("<td>"+data[1]+"</td>");
    } else if (type == "afterItems") {
        var self = this;
        var ret = null;
        if (this.params == undefined || this.params.showDelete != false) {
            var form = $('<form/>',{action:this.url,method:'post'});
            var id = $('<input/>',{type:'hidden',name:'id',value:data});
            var submit = $('<input/>',{type:'submit',name:'delete',value:'delete'});
            form.append(id);
            form.append(submit);

            form.ajaxForm({
                dataType:'json',
                success: function(data) {
                    if (data != null && data.error != undefined) {
                        if (self.errorHandler != undefined) {
                            self.errorHandler("delete",data);
                        }
                    } else {
                        self.refresh(function() { self.renderRead(); });
                    }
                }
            });
            ret = $("<td/>").append(form);
        }

        if (this.params == undefined || this.params.showEdit != false) {
            var edit = $('<a href="">edit</a>');
            edit.click( function() {
                self.renderUpdate(data);
                return false;
            });
            if (ret != null) {
                ret = ret.add( $("<td/>").append(edit) );
            } else {
                ret = $("<td/>").append(edit);
            }
        }

        return ret;
    }
    return null;
}

CRUDrenderer.prototype.headerRenderer = function(type,data) {
    if (type == "wrapper") {
	    return $('<ul></ul>');
    } else if (type == "item") {
        var self=this;
        return $('<li></li>').append( $('<a href="">'+data[0]+'</a>').click(function () {
            data[1](self);
            return false;
        }));
    } else {
        return null;
    }
}

CRUDrenderer.prototype.updateRenderer = function(type,data) {
    if (type == "wrapper") {
        var form = $("<form></form>",{method:'post',action:this.url});
        var self = this;
        form.ajaxForm({
            dataType:'json',
            success:function(data) {
                if (data != null && data.error != undefined) {
                    if (self.errorHandler != undefined) {
                        self.errorHandler("update",data);
                    }
                } else {
                    self.refresh(function() { self.renderRead();} );
                }
            }
        });
        form.append('<table/>');
        return [form,'table'];
    } else if (type == "item") {
        var value = data[1];
        if (value == null) {
            value = "";
        }
        var input = $('<input/>',{type:'text',name:data[0],value:value});
        if (data[0] == 'id') {
            input.attr('type','hidden');
            return input;

        } else {
            if (data[0] == 'password') { input.attr('type','password'); }
            return $("<tr><td>"+data[0]+"</td></tr>").append(
                $("<td></td>").append(input) );
        }
    } else if ( type == "afterItems") {
        var submitText = "edit";
        if (data == null) {
            submitText = "create";
        }
        var input = $("<input/>",{type:'submit',name:'update',value:submitText});
        return $("<tr></tr>").append( $("<td></td>").append(input) );
    }
    return null;
}
